﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Word.Editor.Doc
{
    /// <summary>
    /// The PLC structure is an array of character positions followed by an array of data elements. 
    /// The data elements for any PLC MUST be the same size of zero or more bytes. 
    /// The number of CPs MUST be one more than the number of data elements. 
    /// The CPs MUST appear in ascending order. 
    /// There are different types of PLC structures, as specified in section 2.8. 
    /// Each type specifies whether duplicate CPs are allowed for that type.
    /// </summary>
    public class PLC : IPLC
    {
        public PLC()
        {
        }

        public PLC(byte[] buffer)
        {
            if (buffer.Length <= 0)
                throw new ArgumentOutOfRangeException(Strings.Buffer_LenghtOutOfRange);
        }

        protected UInt32 GetUint32(byte[] buffer, int start)
        {
            UInt32 ret = 0;
            if (start >= buffer.Length || start + 4 > buffer.Length)
                return 0;

            ret = (UInt32)(((((buffer[start + 3] << 8) | buffer[start + 2]) << 8) | buffer[start + 1]) << 8 | buffer[start]);
            return ret;
        }

        public static UInt16 GetUint16(byte[] buffer, int start)
        {
            UInt16 ret = 0;
            if (start >= buffer.Length || start + 2 > buffer.Length)
                return 0;

            ret = (UInt16)((buffer[start + 1]) << 8 | buffer[start]);
            return ret;
        }

        public static Byte GetByte(byte[] buffer, int start)
        {
            Byte ret = 0;
            if (start >= buffer.Length || start + 1 > buffer.Length)
                return 0;

            ret = buffer[start];
            return ret;
        }

        public static Byte[] GetBytes(byte[] buffer, int start,int count)
        {
            Byte[] ret = new Byte[count];
            if (start >= buffer.Length || start + count > buffer.Length)
                return null;

            for (int i = 0; i < count; i++)
                ret[i] = buffer[start + i];
            return ret;
        }

        protected UInt32 ConvertToLittleEndian(UInt32 x)
        {
            UInt32 valueShifted =
                (x << 24) |                // Move 4th byte to 1st
                ((x << 8) & 0x00ff0000) |  // Move 2nd byte to 3rd
                ((x >> 8) & 0x0000ff00) |  // Move 3rd byte to 2nd
                (x >> 24);                 // Move 4th byte to 1st
            return valueShifted;
        }

        /// <summary>
        /// Big Endian
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="start"></param>
        /// <returns></returns>
        protected UInt32 GetUint32_BI(byte[] buffer, int start)
        {
            UInt32 ret = 0;
            if (start >= buffer.Length || start + 4 > buffer.Length)
                return 0;

            ret = (UInt32)(((((buffer[start] << 8) | buffer[start + 1]) << 8) | buffer[start + 2]) << 8 | buffer[start + 3]);
            return ret;
        }


        virtual public void ParseBuffer(byte[] buffer)
        {
            if (buffer.Length <= 0)
                throw new ArgumentOutOfRangeException(Strings.Buffer_LenghtOutOfRange);
        }
}
    /// <summary>
    /// The Clx structure is an array of zero, 1, or more Prcs followed by a Pcdt.
    /// </summary>
    public class Clx:PLC
    {
        public PrcData prc = null;
        public Pcdt pcdt = null;
        public Clx()
            : base()
        { 
        
        }
        public Clx(byte[] buffer):base(buffer)
        {
            if (buffer[0] == 0x02)
                ReadPcdt(buffer);
            else
                ReadPrc();
        }

        public override void ParseBuffer(byte[] buffer)
        {
            base.ParseBuffer(buffer);
            if (buffer[0] == 0x02)
                ReadPcdt(buffer);
            else
                ReadPrc();
        }

        private void ReadPrc()
        {
            ///b.If .clxt = 0x01, read the next 2 bytes as a signed integer, and then skip ahead that number of bytes to the next member of the array.
            throw new NotImplementedException();
        }

        private void ReadPcdt(byte[] buffer)
        {
            pcdt = new Pcdt();
            pcdt.lcb = GetUint32(buffer, 1);
            pcdt.PcdNo = (pcdt.lcb / 4 - 1) / 3;
            //8.Load the PlcPcd.aPcd array and the PlcPcd.aCp array. The members of these arrays correspond to one another by index value.
            pcdt.aPcd = new Pcd[pcdt.PcdNo];
            pcdt.aCP = new UInt32[pcdt.PcdNo + 1];
            for (int i = 0; i < pcdt.PcdNo + 1; i++)
            {
                pcdt.aCP[i] = GetUint32(buffer,5 + i*4);
            }
            for (uint i = 0; i < pcdt.PcdNo; i++)
            {
                pcdt.aPcd[i] = ReadPcd(buffer, 5 + (pcdt.PcdNo + 1) * 4 + i * 8);
            }

        }

        private Pcd ReadPcd(byte[] buffer, uint p)
        {
            Pcd ret = new Pcd();
            ret.fc = GetUint32(buffer, (int)p+2);
            ret.WDSTextStart = (ret.fc<< 2) >>2 ;

            ret.compressed = (((ret.fc >> 30) & 0x1) == 0x1)? true: false;
            if (ret.compressed)
                ret.WDSTextStart = ret.WDSTextStart / 2;
            return ret;
        }


        internal void AddToTree(System.Windows.Forms.TreeNode clxTN)
        {
            TreeNode aCPTN = clxTN.Nodes.Add("aCP");
            for (int i = 0; i < pcdt.PcdNo + 1; i++)
            {
                aCPTN.Nodes.Add("aCP" + i.ToString(), string.Format("{0}={1:x}", i, pcdt.aCP[i]));
            }
            TreeNode aPcdTN = clxTN.Nodes.Add("aPcd");
            for (uint i = 0; i < pcdt.PcdNo; i++)
            {
                aPcdTN.Nodes.Add("aPcd" + i.ToString(), string.Format("{0}={1:x}", i, pcdt.aPcd[i].WDSTextStart));
            }
        }
    }
    /// <summary>
    /// The Prc structure specifies a set of properties for document content that is referenced by a Pcd structure.
    /// </summary>
    public class PrcData
    {
        /// <summary>
        /// A signed integer that specifies the size of GrpPrl, in bytes. This value MUST be less than or equal to 0x3FA2.
        /// </summary>
        public Int16 cbGrpprl;
        /// <summary>
        /// An array of Prl elements. GrpPrl contains a whole number of Prl elements.
        /// </summary>
        public Prl[] GrpPrl;
    }

    public class Prl
    {
        /// <summary>
        /// A Sprm which specifies the property to be modified.
        /// </summary>
        public Sprm sprm = null;
        /// <summary>
        /// A variable-length operand for the sprm. 
        /// The size of the operand is specified by sprm.spra. 
        /// The meaning of the operand depends on the sprm, see section 2.6 (Single Property Modifiers).
        /// </summary>
        public byte[] operand;
        public Prl(UInt16 val) 
        {
            sprm = new Sprm(val);
        }
    }

    /// <summary>
    /// The Pcdt structure contains a PlcPcd structure and specifies its size.
    /// </summary>
    public class Pcdt
    {
        public UInt32 PcdNo = 0;
        /// <summary>
        /// An unsigned integer that specifies the size, in bytes, of the PlcPcd structure.
        /// </summary>
        public UInt32 lcb;
        /// <summary>
        /// The number of CPs MUST be one more than the number of data elements.
        /// The CPs MUST appear in ascending order
        /// </summary>
        public UInt32[] aCP; 
        public Pcd[] aPcd; 
    }
    /// <summary>
    /// The Pcd structure specifies the location of text in the WordDocument Stream and additional properties for this text. 
    /// A Pcd structure is an element of a PlcPcd structure.
    /// (8 bytes each).
    /// </summary>
    public class Pcd
    {
        /// <summary>
        /// If this bit is 1, the text MUST NOT contain a paragraph mark.
        /// </summary>
        public Boolean fNoParaLast;
        /// <summary>
        /// An FcCompressed structure that specifies the location of the text in the WordDocument Stream.
        /// </summary>
        public UInt32 fc;
        /// <summary>
        /// An unsigned integer that specifies an offset in the WordDocument Stream where the text starts. 
        /// If fCompressed is zero, the text is an array of 16-bit Unicode characters starting at offset fc. 
        /// If fCompressed is 1, the text starts at offset fc/2 and is an array of 8-bit Unicode characters, 
        /// except for the values which are mapped to Unicode characters as follows.
        /// </summary>
        public UInt32 WDSTextStart;
        public Boolean compressed;
        /// <summary>
        /// A Prm structure that specifies additional properties for this text. These properties are used as part of the algorithms in sections 2.4.6.1 (Direct Paragraph Formatting) and 2.4.6.2 (Direct Character Formatting).
        /// </summary>
        public UInt16 prm;
    }


}
